package cn.yuanqiao.archive.wenjianzhengli.utils.convert.impl.jave;

import cn.hutool.system.SystemUtil;
import cn.yuanqiao.archive.wenjianzhengli.utils.ImageFormatUtils;
import cn.yuanqiao.archive.wenjianzhengli.utils.convert.MediaFormatConvert;
import cn.yuanqiao.archive.wenjianzhengli.utils.convert.ProgressHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import ws.schild.jave.Encoder;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.ScreenExtractor;
import ws.schild.jave.encode.AudioAttributes;
import ws.schild.jave.encode.EncodingAttributes;
import ws.schild.jave.encode.VideoAttributes;
import ws.schild.jave.filtergraphs.OverlayWatermark;
import ws.schild.jave.filters.helpers.OverlayLocation;
import ws.schild.jave.info.AudioInfo;
import ws.schild.jave.info.MultimediaInfo;
import ws.schild.jave.info.VideoInfo;
import ws.schild.jave.process.ProcessLocator;
import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator;
import ws.schild.jave.progress.EncoderProgressListener;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Component
public class FFmpegMediaFormatConvert extends MediaFormatConvert {
    private static final Logger logger = LoggerFactory.getLogger(FFmpegMediaFormatConvert.class);
    /*
     * exe加载路径
     * */
    private final ProcessLocator processLocator = new DefaultFFMPEGLocator();

    public ProcessLocator getProcessLocator() {
        return processLocator;
    }


    public static class FFmpegEncoderProgressListener implements EncoderProgressListener {
        private final String uuid;

        public FFmpegEncoderProgressListener(String uuid) {
            this.uuid = uuid;
        }


        @Override
        public void sourceInfo(MultimediaInfo multimediaInfo) {
            logger.info("转换媒体信息===>" + multimediaInfo.toString());
        }

        @Override
        public void progress(int i) {
            String message = "格式转换进度===>" + i / 10d + "%";
            ProgressHolder.put(uuid, message);
            logger.info(message);
        }

        @Override
        public void message(String message) {
            logger.info("格式转换信息" + message);
        }
    }

    //private static final EncoderProgressListener encoderProgressListener = new FFmpegEncoderProgressListener();

    /*
     * 媒体对象
     * */
    public MultimediaObject getMultimediaObject(URL file) {
        return new MultimediaObject(file);
    }

    /*
     * 媒体对象
     * */
    public MultimediaObject getMultimediaObject(File file) {
        return new MultimediaObject(file, getProcessLocator());
    }

    /*
     * 判断音频
     * */
    public final boolean isAudio(MultimediaObject multimediaObject) throws Exception {
        final MultimediaInfo multimediaObjectInfo = multimediaObject.getInfo();
        return multimediaObjectInfo.getAudio() != null && multimediaObjectInfo.getVideo() == null;
    }

    /*
     * 判断图像
     * */
    public final boolean isImage(MultimediaObject multimediaObject) throws Exception {
        final MultimediaInfo multimediaObjectInfo = multimediaObject.getInfo();
        return multimediaObjectInfo.getAudio() == null && multimediaObjectInfo.getVideo() != null;
    }

    /*
     * 判断视频
     * */
    public final boolean isVideo(MultimediaObject multimediaObject) throws Exception {
        final MultimediaInfo multimediaObjectInfo = multimediaObject.getInfo();
        return multimediaObjectInfo.getAudio() != null && multimediaObjectInfo.getVideo() != null;
    }

    /*
     * 截图
     * */
    public ScreenExtractor getScreenExtractor() {
        return new ScreenExtractor(getProcessLocator());
    }

    /*
     * 水印
     * imgFile 图片文件
     * 水印位置 1 左上角  2 右上角 3 左下角 4 右下角
     * x 横轴偏移量
     * y 竖轴偏移量
     * */
    public OverlayWatermark getOverlayWatermark(File imgfile, int overlay, int x, int y) {
        OverlayLocation location = OverlayLocation.TOP_LEFT;
        switch (overlay) {
            case 1:
                location = OverlayLocation.TOP_LEFT;
                break;
            case 2:
                location = OverlayLocation.TOP_RIGHT;
                break;
            case 3:
                location = OverlayLocation.BOTTOM_LEFT;
                break;
            case 4:
                location = OverlayLocation.BOTTOM_RIGHT;
                break;
        }
        return new OverlayWatermark(imgfile, location, x, y);
    }

    /*
     * 核心方法
     * */
    public void execute(UUID uuid, Path input, String inputFormat, Path output, String outputFormat, OverlayWatermark overlayWatermark) throws Exception {
        logger.info("input {}, inputFormat {}, output {}, outputFormat {}", input, inputFormat, output, outputFormat);
        EncodingAttributes encodingAttributes = new EncodingAttributes();
        String inputFilenameExtension = Objects.requireNonNull(StringUtils.getFilenameExtension(input.toFile().getName()));
        String outputFilenameExtension = Objects.requireNonNull(StringUtils.getFilenameExtension(output.toFile().getName()));
        /*
         * ps 格式单独处理
         * */
        if (inputFilenameExtension.equalsIgnoreCase("psd") || inputFilenameExtension.equalsIgnoreCase("psb")) {
            ImageFormatUtils.converter(input.toFile(), output.toFile(), outputFilenameExtension);
            return;
        }
        /*
         * 获取媒体信息
         * */
        final MultimediaObject multimediaObject = getMultimediaObject(input.toFile());
        final MultimediaInfo multimediaObjectInfo = multimediaObject.getInfo();
        final AudioInfo audioInfo = multimediaObjectInfo.getAudio();
        final VideoInfo videoInfo = multimediaObjectInfo.getVideo();
        if (audioInfo != null) {
            AudioAttributes audioAttributes = new AudioAttributes();
            encodingAttributes.setAudioAttributes(audioAttributes);
        } else {
            logger.info("没有音频信息，程序判断文件为图像");
        }
        if (videoInfo != null) {
            VideoAttributes videoAttributes = new VideoAttributes();
            if (overlayWatermark != null) {
                logger.info("添加水印...");
                videoAttributes.addFilter(overlayWatermark);
            }

            setOutputFormat(videoAttributes, outputFilenameExtension);
            if (outputFilenameExtension.toLowerCase().endsWith(".mvb")) {
                encodingAttributes.setOutputFormat("mov");
            }
            if (outputFilenameExtension.toLowerCase().endsWith(".rmvb")) {
                encodingAttributes.setOutputFormat("mov");
            }

            videoAttributes.setQuality(2);
            encodingAttributes.setVideoAttributes(videoAttributes);
        } else {
            logger.info("没有视频信息，程序判断文件为音频");
        }
        if (audioInfo == null && videoInfo != null) {
            logger.info("输入文件 {} 图像文件...", input);
        }
        if (audioInfo != null && videoInfo == null) {
            logger.info("输入文件 {} 音频文件...", input);
        }
        if (audioInfo != null && videoInfo != null) {
            logger.info("输入文件 {} 视频文件...", input);
        }
        if (audioInfo == null && videoInfo == null) {
            logger.error("输入文件 {} 不是图片、音频、视频文件或已经损坏", input);
            throw new IllegalArgumentException("输入文件 " + input + " 不是图片、音频、视频文件或已经损坏");
        }
        Encoder encoder = new Encoder(getProcessLocator());
        logger.info("转换开始 input {} ---> output {}", input, output);
        encoder.encode(multimediaObject, output.toFile(), encodingAttributes, new FFmpegEncoderProgressListener(uuid.toString()));
        logger.info("转换结束 input {} ---> output {}", input, output);
    }

    private void setOutputFormat(VideoAttributes videoAttributes, String extension) {
        //todo 输入格式 某些特殊格式不指定会报错
        switch (extension) {
            case "raw":
                videoAttributes.setCodec("png");
                videoAttributes.setPixelFormat("rgba");
                break;
            case "mvb":
            case "fmvb":
                videoAttributes.setCodec("libx264");
                videoAttributes.setPixelFormat("yuv420p");
                break;
        }
    }

    private static final ExecutorService executorService = Executors.newWorkStealingPool();

    public static boolean exe(String name, String... command) {
        boolean flag = false;
        final ProcessBuilder processBuilder = new ProcessBuilder(command);
        Process process = null;
        try {
            logger.info(name + "命令 执行开始 进程信息 " + processBuilder.command());
            // 执行 FFMpeg 命令
            process = processBuilder.start();
            // 获取命令执行的输出信息
            Process finalProcess = process;
            // 写子进程的输出流或输入流出现失败，则可能导致子进程阻塞，甚至产生死锁,所以要异步。
            executorService.execute(() -> {
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(finalProcess.getInputStream()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        logger.info(name + "命令 " + Thread.currentThread().getName() + "日志输出 " + line);
                    }
                } catch (IOException e) {
                    logger.info(name + "命令 " + Thread.currentThread().getName() + "异常信息输出 " + e);
                }
            });
            // 写子进程的输出流或输入流出现失败，则可能导致子进程阻塞，甚至产生死锁，所以要异步。
            executorService.execute(() -> {
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(finalProcess.getErrorStream()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        logger.info(name + "命令 " + Thread.currentThread().getName() + "日志输出 " + line);
                    }
                } catch (IOException e) {
                    logger.info(name + "命令 " + Thread.currentThread().getName() + "异常信息输出 " + e);
                }
            });
            // 等待命令执行完成
            final int exitCode = process.waitFor();
            if (exitCode == 0) {
                logger.info(name + "命令 执行完成 正常退出 退出代码 " + exitCode);
                flag = true;
            } else {
                logger.info(name + "命令 执行完成 异常退出 退出代码" + exitCode);
            }
        } catch (IOException | InterruptedException e) {
            logger.error(name + "命令 异常信息", e);
        } finally {
            // 防止没有释放
            if (process != null) {
                process.destroy();
            }
        }
        return flag;
    }

    /*
     * 转换方法 通用 音视频 图片
     * */
    @Override
    public final void convert(UUID uuid, Path input, String inputFormat, Path output, String outputFormat) throws Exception {
        if (input.equals(output)) {
            throw new IllegalArgumentException("输入文件 和 输出文件 不可以相同！");
        }
        //execute(uuid, input, inputFormat, output, outputFormat, null);
        String ffmpegPath = getProcessLocator().getExecutablePath();
        String inputPath = input.toAbsolutePath().toString();
        String outputPath = output.toAbsolutePath().toString();
        // 根据后缀名判断
        String inputFileName = input.toFile().getName().toLowerCase();
        String outFileName = output.toFile().getName().toLowerCase();
        //  特殊格式处理
        if (inputFileName.endsWith(".psd") || inputFileName.endsWith(".psb")) {
            //File tempFile = File.createTempFile(UUID.randomUUID().toString(), ".jpg");
            //ImageUtils.converter(input.toFile(), tempFile, "jpg");
            //inputPath = tempFile.getAbsolutePath();
            String extension = StringUtils.getFilenameExtension(output.toFile().getName());
            Path path = imageMagickConvert(input, extension);
            Files.move(path, output);
        }
        if (outFileName.endsWith(".raw")) {
            exe("FFMpeg", ffmpegPath, "-y", "-i", inputPath, "-vcodec", "png", "-f", "rawvideo", "-pix_fmt", "rgba", "-q", "2", outputPath);
            return;
        }
        if (outFileName.endsWith(".mvb")) {
            exe("FFMpeg", ffmpegPath, "-y", "-i", inputPath, "-vcodec", "libx264", "-f", "mov", "-pix_fmt", "yuv420p", "-q", "2", outputPath);
            return;
        }
        if (outFileName.endsWith(".rmvb")) {
            exe("FFMpeg", ffmpegPath, "-y", "-i", inputPath, "-vcodec", "libx264", "-f", "mov", "-pix_fmt", "yuv420p", "-q", "2", outputPath);
            return;
        }
        // 常见格式处理
        exe("FFMpeg", ffmpegPath, "-y", "-i", inputPath, "-q", "2", outputPath);
    }

    @Override
    public boolean isImage(File file) throws Exception {
        return isImage(getMultimediaObject(file));
    }

    @Override
    public boolean isVideo(File file) throws Exception {
        return isVideo(getMultimediaObject(file));
    }

    @Override
    public boolean isAudio(File file) throws Exception {
        return isAudio(getMultimediaObject(file));
    }

    /*
     * 截取一张图片
     * */
    public int screenshot(File inputFile, File outputFile, int width, int height, long millis, int quality) throws Exception {
        final ScreenExtractor screenExtractor = getScreenExtractor();
        final MultimediaObject multimediaObject = getMultimediaObject(inputFile);
        screenExtractor.renderOneImage(multimediaObject, width, height, millis, outputFile, quality);
        return screenExtractor.getNumberOfScreens();
    }

    /*
     * 截取多张图片
     * */
    public int screenshot(File inputFile, File outputDir, int width, int height, int seconds, int quality) throws Exception {
        final ScreenExtractor screenExtractor = getScreenExtractor();
        final MultimediaObject multimediaObject = getMultimediaObject(inputFile);
        screenExtractor.render(multimediaObject, width, height, seconds, outputDir, quality);
        return screenExtractor.getNumberOfScreens();
    }

    /*
     * 转换为可直接查看播放文件 通用
     * */
    @Override
    public final Path convertViewFile(UUID uuid, Path input) throws Exception {
        final MultimediaObject multimediaObject = getMultimediaObject(input.toFile());
        Path viewFilePath = getViewFilePath(input);
        if (isAudio(multimediaObject)) {
            viewFilePath = Paths.get(viewFilePath + ".mp3");
        }
        if (isImage(multimediaObject)) {
            viewFilePath = Paths.get(viewFilePath + ".jpg");
        }
        if (isVideo(multimediaObject)) {
            viewFilePath = Paths.get(viewFilePath + ".mp4");
        }
        convert(uuid, input, viewFilePath);
        return viewFilePath;
    }

    /*
     * 转换为缩略图 仅是视频 图片
     * */
    @Override
    public final Path convertThumbnailFile(Path input, int w, int h) throws Exception {
        final Path thumbnailPath = getThumbnailPath(input);
        screenshot(input.toFile(), thumbnailPath.toFile(), w, h, 0L, 75);
        return thumbnailPath;
    }

    public static void convertMVB() throws EncoderException {
        MultimediaObject multimediaObject = new MultimediaObject(new File("a.avi"));
        EncodingAttributes encodingAttributes = new EncodingAttributes();
        VideoAttributes videoAttributes = new VideoAttributes();
        encodingAttributes.setVideoAttributes(videoAttributes);
        AudioAttributes audioAttributes = new AudioAttributes();
        encodingAttributes.setAudioAttributes(audioAttributes);
        encodingAttributes.setOutputFormat("mov");
        encodingAttributes.setDecodingThreads(10);
        Encoder encoder = new Encoder();
        encoder.encode(multimediaObject, new File("D:\\testfile\\b.MVB"), encodingAttributes);
    }

    public static void convertRMVB() throws EncoderException {
        MultimediaObject multimediaObject = new MultimediaObject(new File("a.avi"));
        EncodingAttributes encodingAttributes = new EncodingAttributes();
        VideoAttributes videoAttributes = new VideoAttributes();
        encodingAttributes.setVideoAttributes(videoAttributes);
        AudioAttributes audioAttributes = new AudioAttributes();
        encodingAttributes.setAudioAttributes(audioAttributes);
        encodingAttributes.setOutputFormat("mov");
        encodingAttributes.setDecodingThreads(10);
        Encoder encoder = new Encoder();
        encoder.encode(multimediaObject, new File("D:\\testfile\\b.RMVB"), encodingAttributes);
    }

    public static Path imageMagickConvert(Path inputPath, String filenameExtension) {
        String convert = "convert";
        if (SystemUtil.getOsInfo().isWindows()) {
            String workingDirectory = SystemUtil.getUserInfo().getCurrentDir();
            convert = Paths.get(workingDirectory, "ImageMagick", "convert.exe").toAbsolutePath().toString();
        }
        String name = StringUtils.stripFilenameExtension(inputPath.toFile().getName());
        Path dir = Paths.get(inputPath.toFile().getParent(), "convert");
        Path path = Paths.get(inputPath.toFile().getParent(), "convert", name + "." + filenameExtension);
        if (!Files.isDirectory(dir)) {
            try {
                Files.createDirectories(dir);
            } catch (IOException e) {
                logger.error(e.getMessage());
            }
        }
        exe("ImageMagick", convert, inputPath.toAbsolutePath().toString(), path.toAbsolutePath().toString());
        if (Files.notExists(path)) {
            File file = path.toFile();
            Path filePath = Paths.get(dir.toString(), name + "-0." + filenameExtension);
            if (Files.exists(filePath)) {
                return filePath;
            }
        } else {
            return path;
        }
        throw new RuntimeException("ImageMagick 图像转换失败");
    }

    public static void main1(String[] args) throws Exception {
        //FFmpegMediaFormatConvert formatConvert = new FFmpegMediaFormatConvert();
        //formatConvert.convert(UUID.randomUUID(), new File("D:\\test\\2tif.png"), new File("D:\\test\\2233.raw"));
        MultimediaObject multimediaObject1 = new MultimediaObject(new File("D:\\testfile\\a.MVB"));
        MultimediaInfo info1 = multimediaObject1.getInfo();
        System.out.println(info1.getFormat());
        System.out.println(info1.getAudio());
        System.out.println(info1.getVideo());
        System.out.println("##########################################################################################");
//        MultimediaObject multimediaObject2 = new MultimediaObject(new File("D:\\testfile\\a.RMVB"));
//        MultimediaInfo info2 = multimediaObject2.getInfo();
//        System.out.println(info2.getFormat());
//        System.out.println(info2.getAudio());
//        System.out.println(info2.getVideo());

        EncodingAttributes encodingAttributes = new EncodingAttributes();

        VideoAttributes videoAttributes = new VideoAttributes();
        //videoAttributes.setCodec("h264");
        //videoAttributes.setPixelFormat("yuv420p");
        encodingAttributes.setVideoAttributes(videoAttributes);

        AudioAttributes audioAttributes = new AudioAttributes();
        //audioAttributes.setCodec("aac");
        //audioAttributes.setChannels(2);
        encodingAttributes.setAudioAttributes(audioAttributes);

        encodingAttributes.setOutputFormat("mov");
        encodingAttributes.setDecodingThreads(10);

        Encoder encoder = new Encoder();
        encoder.encode(multimediaObject1, new File("D:\\testfile\\b.MVB"), encodingAttributes);

        System.out.println("##########################################################################################");
        MultimediaObject multimediaObject3 = new MultimediaObject(new File("D:\\testfile\\b.MVB"));
        MultimediaInfo info3 = multimediaObject3.getInfo();
        System.out.println(info3.getFormat());
        System.out.println(info3.getAudio());
        System.out.println(info3.getVideo());
    }

    public static void main2(String[] args) throws Exception {
        //FFmpegMediaFormatConvert formatConvert = new FFmpegMediaFormatConvert();
        //formatConvert.convert(UUID.randomUUID(), new File("D:\\test\\2tif.png"), new File("D:\\test\\2233.raw"));
        MultimediaObject multimediaObject1 = new MultimediaObject(new File("D:\\testfile\\a.RMVB"));
        MultimediaInfo info1 = multimediaObject1.getInfo();
        System.out.println(info1.getFormat());
        System.out.println(info1.getAudio());
        System.out.println(info1.getVideo());
        System.out.println("##########################################################################################");
//        MultimediaObject multimediaObject2 = new MultimediaObject(new File("D:\\testfile\\a.RMVB"));
//        MultimediaInfo info2 = multimediaObject2.getInfo();
//        System.out.println(info2.getFormat());
//        System.out.println(info2.getAudio());
//        System.out.println(info2.getVideo());

        EncodingAttributes encodingAttributes = new EncodingAttributes();

        VideoAttributes videoAttributes = new VideoAttributes();
        //videoAttributes.setCodec("h264");
        //videoAttributes.setPixelFormat("yuv420p");
        encodingAttributes.setVideoAttributes(videoAttributes);

        AudioAttributes audioAttributes = new AudioAttributes();
        //audioAttributes.setCodec("aac");
        //audioAttributes.setChannels(2);
        encodingAttributes.setAudioAttributes(audioAttributes);

        encodingAttributes.setOutputFormat("mov");
        encodingAttributes.setDecodingThreads(10);

        Encoder encoder = new Encoder();
        encoder.encode(multimediaObject1, new File("D:\\testfile\\b.RMVB"), encodingAttributes);

        System.out.println("##########################################################################################");
        MultimediaObject multimediaObject3 = new MultimediaObject(new File("D:\\testfile\\b.RMVB"));
        MultimediaInfo info3 = multimediaObject3.getInfo();
        System.out.println(info3.getFormat());
        System.out.println(info3.getAudio());
        System.out.println(info3.getVideo());
    }

    public static void main3(String[] args) throws Exception {
        //FFmpegMediaFormatConvert formatConvert = new FFmpegMediaFormatConvert();
        //formatConvert.convert(UUID.randomUUID(), new File("D:\\test\\2tif.png"), new File("D:\\test\\2233.raw"));
        MultimediaObject multimediaObject1 = new MultimediaObject(new File("D:\\testfile\\21.flv"));
        // png_pipe
        MultimediaInfo info1 = multimediaObject1.getInfo();
        System.out.println(info1.getFormat());
        System.out.println(info1.getAudio());
        System.out.println(info1.getVideo());

        EncodingAttributes encodingAttributes = new EncodingAttributes();
        VideoAttributes videoAttributes = new VideoAttributes();
        encodingAttributes.setVideoAttributes(videoAttributes);
        encodingAttributes.setOutputFormat("png_pipe");
        Encoder encoder = new Encoder();
        encoder.encode(multimediaObject1, new File("D:\\testfile\\d.raw"), encodingAttributes);
    }

    public static void main(String[] args) throws Exception {
        //main3(args);
        //new FFmpegMediaFormatConvert().convert(UUID.randomUUID(),"D:\\testfile\\21.flv","D:\\testfile\\c.RMVB");
        //new FFmpegMediaFormatConvert().convert(UUID.randomUUID(),"D:\\testfile\\21.flv","D:\\testfile\\c.raw");
        //imageMagickConvert(Paths.get("D:\\testfile\\测试psd文件.psd"), Paths.get("D:\\testfile\\测试psd文件.jpg"));
        //imageMagickConvert(Paths.get("D:\\testfile\\18.pptx"), Paths.get("D:\\testfile\\18.pptx.jpg"));
        //imageMagickConvert(Paths.get("D:\\testfile\\17.ppt"), Paths.get("D:\\testfile\\17.ppt.jpg"));
        //imageMagickConvert(Paths.get("D:\\testfile\\20.pdf"), Paths.get("D:\\testfile\\20.pdf.jpg"));

        System.out.println(imageMagickConvert(Paths.get("D:\\testfile\\测试psd文件.psd"), "png"));
    }
}
